/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: service_requester.cpp
 *
 * Purpose: the requester default implmentation for service
 *
 * Developer:
 *   wen.gu , 2021-08-05
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "icpp/com/service_requester.h"
#include <thread>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <future>
#include <atomic>


#include "icpp/core/utils.h"
#include "icpp/core/plugin_manager.h"
#include "icpp/com/message_broker_simple.h"
#include "icpp/com/proxy_method.h"
#include "icpp/com/service_proxy.h"

#define LOG_TAG "CliR"
#include "icpp/core/log.h"

namespace icpp
{
namespace com
{
/******************************************************************************
 **    MACROS
 ******************************************************************************/
/** how long to try connect to service for connect async */
#define TRY_CONNECT_INTERVAL_MS 50 
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

class ServiceRequester::Impl
{
public:
    uint8_t interface_version_;

    MessageBroker::MessageBrokerPtr msg_broker_ = nullptr;
    ServiceProxy* proxy_;
public:
    Impl(ServiceFinder::ServiceFinderPtr service_finder, uint8_t interface_version);
    ~Impl();
public:

    Result<MessagePtr> invoke(MessageType type, MessageType reply_type, MessageId method_id, PayloadPtr param_ptr, int32_t timeout_ms);
    
};
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
ServiceRequester::Impl::Impl(ServiceFinder::ServiceFinderPtr service_finder, uint8_t interface_version)
    :proxy_(new ServiceProxy(service_finder, interface_version))
    
{
    msg_broker_ = proxy_->broker();
    /** todo something */
}

ServiceRequester::Impl::~Impl()
{
    /** todo something */    
    delete proxy_;
}

Result<Message::MessagePtr> ServiceRequester::Impl::invoke(MessageType type, MessageType reply_type, MessageId method_id, PayloadPtr param_ptr, int32_t timeout_ms)
{
    Response<MessagePtr> res = MessageMethod(msg_broker_, type, reply_type, method_id, param_ptr);

    if (timeout_ms <= INVOKE_WAIT_INFINITE)
    {
        res.wait();
        return res.getResult();
    }

    if (res.waitFor(std::chrono::milliseconds(timeout_ms)) == ResponseStatus::kReady)
    {
        return res.getResult();
    }

    return Result<MessagePtr>::FromError(IcppErrc::Timeout);
}

/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
ServiceRequester::ServiceRequester(ServiceFinder::ServiceFinderPtr service_finder, uint8_t interface_version /*= 0*/)
    :impl_(new Impl(service_finder, interface_version))

{
    /** todo something */
}

ServiceRequester::~ServiceRequester()
{
    /** todo something */
    disconnect();
}

/**
 * start to connect to service and wait unitil timeout or successfull connect to service
 */
ServiceRequester::IcppErrc ServiceRequester::connect(int32_t timeout_ms /*= CONNECT_WAIT_INFINITE*/)
{
    return impl_->proxy_->connect(timeout_ms);
}

/**
 * try to connect to service, and return result immediately.
*/
ServiceRequester::IcppErrc ServiceRequester::tryConnect()
{
    return impl_->proxy_->tryConnect();
}

/**
 * cancel connet  when Connect method called and in waiting state. 
 */
void ServiceRequester::cancelConnect()
{
    return impl_->proxy_->cancelConnect();
}

/** disconnect from service*/
ServiceRequester::IcppErrc ServiceRequester::disconnect()
{
    return impl_->proxy_->disconnect();
}


ServiceRequester::IcppErrc ServiceRequester::startConnectAsync(ConnectHandler handler)
{
    return impl_->proxy_->startConnectAsync(handler);
}

ServiceRequester::IcppErrc ServiceRequester::stopConnectAsync()
{
    return impl_->proxy_->stopConnectAsync();
}



/** invoke with sync mode, this API will not return until reply is received */
Result<Message::MessagePtr> ServiceRequester::invoke(MessageId method_id, PayloadPtr param_ptr, int32_t  timeout_ms /*= INVOKE_WAIT_DEFAULT_MS*/)
{    
    return impl_->invoke(MessageType::kRequest, MessageType::kResponse, method_id, param_ptr, timeout_ms);
}

/** invoke with async mode, when the on_reply be called by receive thread, then it will be removed */
ServiceRequester::IcppErrc ServiceRequester::invokeAsync(MessageId method_id, PayloadPtr param_ptr, MessageBroker::MessageHandler on_reply)
{
    return MessageMethodAsync(impl_->msg_broker_, MessageType::kRequest, MessageType::kResponse, method_id, param_ptr, on_reply);
}

ServiceRequester::IcppErrc ServiceRequester::invokeNoReturn(MessageId method_id, PayloadPtr param_ptr)
{
    return MessageMethodAsync(impl_->msg_broker_, MessageType::kRequestNoReturn, MessageType::kResponse, method_id, param_ptr, nullptr); 
}


ServiceRequester::IcppErrc ServiceRequester::subscribe(MessageId event_id, EventHandler handler)
{
    /**todo, is need to wait for subscribe ack ?? */
    return impl_->msg_broker_->subscribe(MessageType::kNotification, MessageType::kSubscribe, event_id, handler);   
}

ServiceRequester::IcppErrc ServiceRequester::subscribe(const EventHandlerMap& event_group)
{
    for (auto& it: event_group)
    {
        subscribe(it.first, it.second);
    }
    /** todo, process subcribe error, is need add param e.g  bool ignore_error */

    return IcppErrc::OK;
}

ServiceRequester::IcppErrc ServiceRequester::unsubscribe(MessageId event_id)
{
    /**todo, is need to wait for unsubscribe ack ?? */
    return impl_->msg_broker_->unsubscribe(MessageType::kNotification, MessageType::kUnsubscribe, event_id);
}

ServiceRequester::IcppErrc ServiceRequester::unsubscribe(const EventHandlerMap& event_group)
{
    for (auto& it: event_group)
    {
        unsubscribe(it.first);
    }
    /** todo, process subcribe error, is need add param e.g  bool ignore_error */

    return IcppErrc::OK;
}

/** for message: create, serialize, deserialize */
ServiceRequester::MessagePtr ServiceRequester::newMessage(MessageType type, MessageId method_id, PayloadPtr payload_ptr) 
{
    if (impl_->proxy_)
    {
        return impl_->proxy_->newMessage()
    }
}

ServiceRequester::SerializerPtr ServiceRequester::newSerializer()
{
    return impl_->msg_broker_->newSerializer();
}

ServiceRequester::DeserializerPtr ServiceRequester::newDeserializer(PayloadPtr payload)
{
    return impl_->msg_broker_->newDeserializer(payload);
}

MessageBroker::MessageBrokerPtr ServiceRequester::broker()
{
    return impl_->msg_broker_;
}

Result<Message::MessagePtr> ServiceRequester::propertySet(MessageId property_id, PayloadPtr val_ptr, int32_t  timeout_ms /*= INVOKE_WAIT_DEFAULT_MS*/)
{
    return impl_->invoke(MessageType::kPropertySet, MessageType::kPropertySetAck, property_id, val_ptr, timeout_ms);
}

Result<Message::MessagePtr> ServiceRequester::propertyGet(MessageId property_id, int32_t  timeout_ms /*= INVOKE_WAIT_DEFAULT_MS*/)
{
    return impl_->invoke(MessageType::kPropertyGet, MessageType::kPropertyGetAck, property_id, nullptr, timeout_ms);
}

ServiceRequester::IcppErrc ServiceRequester::propertySubscribe(MessageId property_id, EventHandler handler)
{
    /**todo, is need to wait for subscribe ack ?? */
    return impl_->msg_broker_->subscribe(MessageType::kPropertyNotify, MessageType::kPropertySubcribe, property_id, handler);   
}

ServiceRequester::IcppErrc ServiceRequester::propertyUnsubscribe(MessageId property_id)
{
    /**todo, is need to wait for unsubscribe ack ?? */
    return impl_->msg_broker_->unsubscribe(MessageType::kPropertySubcribe, MessageType::kPropertyUnsubscribe, property_id);   
}


} /** namespace com */
} /** namespace icpp */
